/**
 * Copyright (c) 2007/2008 by Rowan Rodrik van der Molen
 *
 * This file is part of readline-trainer.
 *
 * readline-trainer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * at your option) any later version.
 *
 * readline-trainer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with readline-trainer.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>

#include "lessons.h"
#include "parser.h"

/*
xmlEntityPtr
trrl_parser_get_entity_handler(trrl_parser_state_ptr state, const xmlChar* name)
{
  ent = malloc (sizeof(xmlEntity));
  if (0 == xmlStrcmp(name,  (const xmlChar *)"br")) // new lesson
  {
    ent.
}*/

trrl_parser_state_ptr
trrl_new_parser_state()
{
    trrl_parser_state_ptr state = malloc (sizeof(trrl_parser_state));
    trrl_clear_parser_state(state);
    return state;
}

void
trrl_clear_parser_state(trrl_parser_state_ptr state)
{
    state->lesson_count = 0;
    state->context = TRRL_NOWHERE;
    state->first_lesson = NULL;
    state->last_lesson = NULL;
    state->first_exercise = NULL;
    state->last_exercise = NULL;
}

static void
trrl_parser_grow_string(char** ptr1, const char* ptr2, int ptr2_length)
{
    int ptr1_length = (*ptr1 == NULL ? 0 : strlen(*ptr1));
    //if (ptr2_length == -1) ptr2_length = ptr2 == NULL ? 0 : strlen(ptr2);
    int combined_length = ptr1_length + ptr2_length;
    int i, ii;

    *ptr1 = realloc(*ptr1,  sizeof(char) * (combined_length+1));
    //ptr1 = malloc(sizeof(char) * (combined_length+1));

    for (i = ptr1_length?ptr1_length:0, ii = 0;  ii < ptr2_length;  i++, ii++)
    {
        (*ptr1)[i] = ptr2[ii];
    }

    (*ptr1)[i] = '\0';
}

static void
trrl_parser_set_fg_color_escape(char** ptr, const char* fg_color)
{
    char* fg_color_string = malloc( sizeof(char) * 5 );

    if (0 == strcmp(fg_color,  "black"))
        fg_color_string = "\e[30m";
    else if (0 == strcmp(fg_color, "red"))
        fg_color_string = "\e[31m";
    else if (0 == strcmp(fg_color, "green"))
        fg_color_string = "\e[32m";
    else if (0 == strcmp(fg_color, "brown"))
        fg_color_string = "\e[33m";
    else if (0 == strcmp(fg_color, "blue"))
        fg_color_string = "\e[34m";
    else if (0 == strcmp(fg_color, "magenta"))
        fg_color_string = "\e[35m";
    else if (0 == strcmp(fg_color, "cyan"))
        fg_color_string = "\e[36m";
    else if (0 == strcmp(fg_color, "white"))
        fg_color_string = "\e[37m";

    trrl_parser_grow_string(ptr, fg_color_string, 5);
}


static void
trrl_parser_set_bg_color_escape(char** ptr, const char* bg_color)
{
    char* bg_color_string = malloc( sizeof(char) * 5 );

    if (0 == strcmp(bg_color, "black"))
        bg_color_string = "\e[40m";
    else if (0 == strcmp(bg_color, "red"))
        bg_color_string = "\e[41m";
    else if (0 == strcmp(bg_color, "green"))
        bg_color_string = "\e[42m";
    else if (0 == strcmp(bg_color, "brown"))
        bg_color_string = "\e[43m";
    else if (0 == strcmp(bg_color, "blue"))
        bg_color_string = "\e[44m";
    else if (0 == strcmp(bg_color, "magenta"))
        bg_color_string = "\e[45m";
    else if (0 == strcmp(bg_color, "cyan"))
        bg_color_string = "\e[46m";
    else if (0 == strcmp(bg_color, "white"))
        bg_color_string = "\e[47m";

    trrl_parser_grow_string(ptr, bg_color_string, 5);
}


static void
trrl_parser_default_color_escape(char** ptr)
{
    trrl_parser_grow_string(ptr, (char*)"\e[0m", 4);
}

static void
trrl_parser_bold_escape(char** ptr)
{
    trrl_parser_grow_string(ptr, (char*)"\e[1m", 4);
}

static void
trrl_parser_start_element_handler(trrl_parser_state_ptr state, const xmlChar* name, const xmlChar** attributes)
{
    xmlChar** attr_ptr;

    if (0 == xmlStrcmp(name,  (const xmlChar *)"lesson")) // new lesson
    {
        state->context = TRRL_INSIDE_LESSON;

        trrl_lesson_ptr this_lesson = trrl_new_lesson();

        this_lesson->number = ++(state->lesson_count);

        if (state->last_lesson != NULL)
            state->last_lesson->next_lesson = this_lesson;

        state->last_lesson = this_lesson;

        if (state->first_lesson == NULL)
            state->first_lesson = this_lesson;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"exercise")) // new exercise
    {
        state->context = TRRL_INSIDE_EXERCISE;

        trrl_exercise_ptr new_exercise = trrl_new_exercise();

        new_exercise->line = (char*) xmlStrdup( attributes[1] );

        if ( state->last_exercise != NULL ) {
            if (state->first_exercise == NULL) {
                state->last_lesson->first_exercise = state->first_exercise = state->last_exercise;
            }

            state->last_exercise->next_exercise = new_exercise;
        }
        state->last_exercise = new_exercise;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"br")) // linebreak
    {
        if (TRRL_INSIDE_EXERCISE == state->context)
        {
            //state->last_exercise
        }
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"hint")) // exercise hint
    {
        state->context = TRRL_INSIDE_HINT;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"instruction")) // exercise hint
    {
        state->context = TRRL_INSIDE_INSTRUCTION;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"title")) // lesson title
    {
        state->context = TRRL_INSIDE_LESSON_TITLE;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"bold"))
    {
        if (TRRL_INSIDE_HINT == state->context) {
            trrl_parser_bold_escape(&(state->last_exercise->hint));
        }
        else if (TRRL_INSIDE_INSTRUCTION == state->context) {
            trrl_parser_bold_escape(&(state->last_exercise->instruction));
        }
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"color"))
    {
        for (attr_ptr = (xmlChar**)attributes; *attr_ptr; ++attr_ptr)
        {
            if (0 == xmlStrcmp(*attr_ptr,  (const xmlChar *)"fg")) 
            {
                ++attr_ptr;
                if (TRRL_INSIDE_HINT == state->context) {
                    trrl_parser_set_fg_color_escape(&state->last_exercise->hint, (char*)*attr_ptr);
                }
                else if (TRRL_INSIDE_INSTRUCTION == state->context) {
                    trrl_parser_set_fg_color_escape(&state->last_exercise->instruction, (char*)*attr_ptr);
                }
            }

            if (0 == xmlStrcmp(*attr_ptr,  (const xmlChar *)"bg")) 
            {
                ++attr_ptr;
                if (TRRL_INSIDE_HINT == state->context) {
                    trrl_parser_set_bg_color_escape(&state->last_exercise->hint, (char*)*attr_ptr);
                }
                else if (TRRL_INSIDE_INSTRUCTION == state->context) {
                    trrl_parser_set_bg_color_escape(&state->last_exercise->instruction, (char*)*attr_ptr);
                }
            }
        }
    }
}


static void
trrl_parser_end_element_handler(trrl_parser_state* state, const xmlChar* name)
{
    if (0 == xmlStrcmp(name,  (const xmlChar *)"lesson")) // end lesson
    { 
        state->first_exercise = NULL;
        state->last_exercise = NULL;
        state->context = TRRL_INSIDE_LESSON_LIST;
    }   
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"exercise")) // end of exercise, back in lesson
    { 
        state->context = TRRL_INSIDE_LESSON;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"title")) // end of exercise, back in lesson
    {
        state->context = TRRL_INSIDE_LESSON;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"hint")) // end of hint, back in exercise
    {
        state->context = TRRL_INSIDE_EXERCISE;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"instruction")) // end of instruction, back in exercise
    {
        state->context = TRRL_INSIDE_EXERCISE;
    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"color"))
    {
        if (TRRL_INSIDE_HINT == state->context) {
            trrl_parser_default_color_escape(&state->last_exercise->hint);
        }
        else if (TRRL_INSIDE_INSTRUCTION == state->context) {
            trrl_parser_default_color_escape(&state->last_exercise->instruction);
        }

    }
    else if (0 == xmlStrcmp(name,  (const xmlChar *)"bold"))
    {
        if (TRRL_INSIDE_HINT == state->context) {
            trrl_parser_default_color_escape(&state->last_exercise->hint);
        }
        else if (TRRL_INSIDE_INSTRUCTION == state->context) {
            trrl_parser_default_color_escape(&state->last_exercise->instruction);
        }

    }
}



static void
trrl_parser_char_handler(trrl_parser_state_ptr state, const xmlChar* ch, int len)
{
    if (TRRL_INSIDE_LESSON_TITLE == state->context) {
        state->last_lesson->title = (char*) xmlStrndup(ch, len);
    }
    else if (TRRL_INSIDE_HINT == state->context) {
        trrl_parser_grow_string(&state->last_exercise->hint, (char*)ch, len);
    }
    else if (TRRL_INSIDE_INSTRUCTION == state->context) {
        trrl_parser_grow_string(&state->last_exercise->instruction, (char*)ch, len);
    }

}


static void
trrl_parser_error_handler(trrl_parser_state* state, const char* msg, ...)
{
    va_list args;
    va_start(args, msg);
    vprintf(msg, args);
    va_end(args);
}


trrl_lesson_ptr 
trrl_parse_lessons_file(char* filename)
{
    trrl_parser_state_ptr state = trrl_new_parser_state();
    //trrl_clear_parser_state(&state);

    LIBXML_TEST_VERSION

    xmlSAXHandler sax_handler;
    memset( &sax_handler, 0, sizeof(xmlSAXHandler) );

    sax_handler.initialized = XML_SAX2_MAGIC;
    sax_handler.characters = (void*) &trrl_parser_char_handler;
    sax_handler.startElement = (void*) &trrl_parser_start_element_handler;
    sax_handler.endElement = (void*) &trrl_parser_end_element_handler;
    sax_handler.warning = (void*) &trrl_parser_error_handler;
    sax_handler.error = (void*) &trrl_parser_error_handler;
    //sax_handler.getEntity = &trrl_parser_get_entity_handler;

    if (xmlSAXUserParseFile(&sax_handler, state, filename) != 0) {
        fprintf(stderr, "Document not parsed successfully.\n");
        exit(EXIT_FAILURE);
    }

#ifdef DEBUG_PARSER
    printf("%d lessons parsed\n", state->lesson_count);
#endif

    xmlCleanupParser();

    return state->first_lesson;
}

// vim: expandtab shiftwidth=4 tabstop=4:
